#!/bin/bash

error ()
{
	echo "error: ${@}" >&2
	exit 1
}

# import Cmdline_old()
. /lib/live/boot/9990-cmdline-old \
	|| error 'Could not source /lib/live/boot/9990-cmdline-old'

# Set variable names needed by get_custom_mounts(),
# and now initialized by live-boot in a file that we certainly
# don't want to source.
export persistence_list="persistence.conf"
export old_persistence_list="nonexistent"

# This will import the following functions and variables used below:
#   activate_custom_mounts()
#   get_custom_mounts()
#   open_luks_device()
#   probe_for_gpt_name()
#   removable_dev()
#   removable_usb_dev()
#   storage_devices()
#   where_is_mounted()
#   $custom_overlay_label
. /lib/live/boot/9990-misc-helpers.sh \
	|| error 'Could not source /lib/live/boot/9990-misc-helpers.sh'

usage ()
{
	local cmd=${0##*/}
	echo "Usage: ${cmd} [OPTION]... list [LABEL]...
List (on stdout) all GPT partitions with names among LABEL(s) that are
compatible with live-boot's overlay persistence, and that are adhering to
live-boot's persistence filters (e.g. persistent-media). If no LABEL is given
the default in live-boot is used ('${custom_overlay_label}').
   or: ${cmd} [OPTION]... activate VOLUME...
Activates persistence on the given VOLUME(s). Successes and failures are
written to stdout. There are no checks for whether the given volumes adhere
to live-boot's options.

Kernel command-line options are parsed just like in live-boot and have the same
effect (see live-boot(7) for more information)

Arguments to options must be passed using an equality sign. LISTs are coma
separated. Most options correspond to the persistent-* options of live-boot,
and will override the corresponging options parsed from the kernel command-line.

General options:
  --help                display this help and exit
  --log-file=FILE       log the bash execution trace to FILE

Options affecting the 'list' action:
  --encryption=LIST     override 'persistent-encryption'
  --media=VALUE         override 'persistent-media'

Options affecting the 'activate' action:
  --read-only           enable 'persistent-read-only'
  --read-write          disable 'persistent-read-only'
  --union=VALUE         override 'union'
"
}

escape_dots() {
	printf "%s\n" $1 | sed 's/\./\\./g'
}

migrate_persistence_preset()
{
	local OLD_PRESET="${1}"
	local OLD_PRESET_SOURCE="${2}"
	local NEW_PRESET="${3}"
	local NEW_PRESET_SOURCE="${4}"
	local CONFIG="${5}"
	if grep -E -qs --line-regex \
		-e "$(escape_dots ${OLD_PRESET})\s+source=${OLD_PRESET_SOURCE}" \
		"$CONFIG" \
		&& ! grep -E -qs --line-regex \
		-e "$(escape_dots ${NEW_PRESET})\s+source=${NEW_PRESET_SOURCE}" \
		"$CONFIG"
	then
		warning "Need to make $NEW_PRESET persistent"
		if [ "$PERSISTENCE_READONLY" = true ]
		then
			warning "Persistence configuration needs to be migrated, but read only was selected; please retry in read-write mode"
		else
			echo "$NEW_PRESET	source=$NEW_PRESET_SOURCE" \
				>> "$CONFIG" \
				|| error "Failed to make $NEW_PRESET: $?"
			warning "Successfully made $NEW_PRESET persistent"
		fi
	fi
}

warning ()
{
	echo "warning: ${@}" >&2
}

# We override live-boot's logging facilities to get more useful error messages
log_warning_msg ()
{
	warning ${@}
}

# We override live-boot's panic() since it does a lot of crazy stuff
panic ()
{
	error ${@}
}

list_gpt_volumes ()
{
	local labels=${@}

	local whitelistdev=""
	case "${PERSISTENCE_MEDIA}" in
		removable)
			whitelistdev="$(removable_dev)"
			[ -z "${whitelistdev}" ] && return
			;;
		removable-usb)
			whitelistdev="$(removable_usb_dev)"
			[ -z "${whitelistdev}" ] && return
			;;
		*)
			if grep -qs -w -E '(live-media|bootfrom)=removable-usb' /proc/cmdline ; then
				whitelistdev="$(removable_usb_dev)"
				[ -z "${whitelistdev}" ] && return
			elif grep -qs -w -E '(live-media|bootfrom)=removable' /proc/cmdline ; then
				whitelistdev="$(removable_dev)"
				[ -z "${whitelistdev}" ] && return
			else
				whitelistdev=""
			fi
			;;
	esac

	for dev in $(storage_devices "" "${whitelistdev}")
	do
		if ( is_luks_partition ${dev} >/dev/null 2>&1 && \
		     echo ${PERSISTENCE_ENCRYPTION} | grep -qve "\<luks\>" ) || \

		   ( ! is_luks_partition ${dev} >/dev/null 2>&1 && \
		     echo ${PERSISTENCE_ENCRYPTION} | grep -qve "\<none\>" )
		then
			continue
		fi
		local result="$(probe_for_gpt_name "${labels}" ${dev})"
		if [ -n "${result}" ]
		then
			echo ${result#*=}
		fi
	done

	exit 0
}

mountpoint_has_correct_access_rights ()
{
	local mountpoint="$1"
	local expected_user=root
	local expected_group=root
	local expected_perms=775
	local expected_acl="user::rwx
user:tails-persistence-setup:rwx
group::rwx
mask::rwx
other::r-x"

	if [ $(stat -c %U "$mountpoint") != "$expected_user" ]
	then
		warning "'$mountpoint' is not owned by the '$expected_user' user"
		return 1
	fi
	if [ $(stat -c %G "$mountpoint") != "$expected_group" ]
	then
		warning "'$mountpoint' is not owned by the '$expected_group' group"
		return 2
	fi
	if [ $(stat -c %a "$mountpoint") != "$expected_perms" ]
	then
		warning "'$mountpoint' permissions are not $expected_perms"
		return 4
	fi
	if [ "$(getfacl --omit-header --skip-base "$mountpoint" 2>/dev/null | grep -v '^\s*$')" \
	      != "$expected_acl" ]
	then
		warning "'$mountpoint' has incorrect ACL"
		return 8
	fi
	return 0
}

persistence_conf_file_has_correct_access_rights ()
{
	local conf="$1"
	local expected_user=tails-persistence-setup
	local expected_group=tails-persistence-setup
	local expected_perms=600
	local expected_acl=""

	if [ $(stat -c %U "$conf") != "$expected_user" ]
	then
		warning "'$conf' is not owned by the '$expected_user' user"
		return 1
	fi
	if [ $(stat -c %G "$conf") != "$expected_group" ]
	then
		warning "'$conf' is not owned by the '$expected_group' group"
		return 2
	fi
	if [ $(stat -c %a "$conf") != "$expected_perms" ]
	then
		warning "'$conf' permissions are not $expected_perms"
		return 4
	fi
	if [ "$(getfacl --omit-header --skip-base "$conf" 2>/dev/null | grep -v '^\s*$')" \
	      != "$expected_acl" ]
	then
		warning "'$conf' has incorrect ACL"
		return 8
	fi
	return 0
}

disable_and_create_empty_persistence_conf_file ()
{
	local conf="$1"

	mv "$conf" "${conf}.insecure_disabled" \
		|| error "Failed to disable '$conf': $?"
	install --owner tails-persistence-setup \
		--group tails-persistence-setup --mode 0600 \
		/dev/null "$conf" \
		|| error "Failed to create empty '$conf': $?"
}

activate_volumes ()
{
	local volumes=${@}
	local ret=0
	local open_volumes=""
	local successes=""
	local failures=""

	# required by open_luks_device()
	exec 6>&1

	for vol in ${volumes}
	do
		if [ ! -b "${vol}" ]
		then
			warning "${vol} is not a block device"
			failures="${failures} ${vol}"
			ret=1
			continue
		fi
		if [ -n "$(what_is_mounted_on ${dev})" ]
		then
			warning "${vol} is already mounted"
			failures="${failures} ${vol}"
			ret=1
			continue
		fi
		local luks_vol=""
		if /sbin/cryptsetup isLuks ${vol} >/dev/null 2>&1 
		then
			if luks_vol=$(open_luks_device "${vol}")
			then
				open_volumes="${open_volumes} ${luks_vol}"
			else
				failures="${failures} ${vol}"
			fi
		else
			open_volumes="${open_volumes} ${vol}"
		fi
	done

	custom_mounts="$(mktemp /tmp/custom_mounts-XXXXXX.list)"
	get_custom_mounts ${custom_mounts} ${open_volumes}
	# ... and now the persistent volumes should be mounted.

	# Enable the acl mount option on all persistent filesystems.
	for mountpoint in $(ls -d /live/persistence/*_unlocked || true)
	do
		mount -o remount,acl "$mountpoint"
	done

	# Detect if we have incorrect ownership, permissions and ACL.
	ACCESS_RIGHTS_ARE_CORRECT=true
	for mountpoint in $(ls -d /live/persistence/*_unlocked || true)
	do
		if ! mountpoint_has_correct_access_rights "$mountpoint"
		then
			ACCESS_RIGHTS_ARE_CORRECT=false
			break
		fi
	done

	# Disable all persistence configuration files if the mountpoint
	# has wrong access rights.
	if [ "$ACCESS_RIGHTS_ARE_CORRECT" != true ]
	then
		for f in $(ls /live/persistence/*_unlocked/persistence.conf \
		              /live/persistence/*_unlocked/live-additional-software.conf || true)
		do
			warning "Disabling '$f': persistent volume has unsafe access rights"
			disable_and_create_empty_persistence_conf_file "$f"
		done
	fi

	# Regardless of the mountpoint access rights, disable persistence
	# configuration files with wrong access rights.
	for f in $(ls /live/persistence/*_unlocked/persistence.conf \
	              /live/persistence/*_unlocked/live-additional-software.conf || true)
	do
		if ! persistence_conf_file_has_correct_access_rights "$f"
		then
			warning "Disabling '$f', that has unsafe access rights"
			disable_and_create_empty_persistence_conf_file "$f"
		fi
	done

	for conf in $(ls /live/persistence/*_unlocked/persistence.conf || true)
	do
		# Migrate Squeeze-era NetworkManager persistence setting to Wheezy.
		migrate_persistence_preset '/home/amnesia/.gconf/system/networking/connections' 'nm-connections' \
			'/etc/NetworkManager/system-connections' 'nm-system-connections' "$conf"
		# disable pre-Wheezy NM persistence setting
		sed -r -i \
			-e 's,^(/home/amnesia/\.gconf/system/networking/connections\s+source=nm-connections)$,#\1,' \
			"$conf"

		# Migrate Claws-mail persistence setting to Icedove
		migrate_persistence_preset '/home/amnesia/.claws-mail' 'claws-mail' \
			'/home/amnesia/.icedove' 'icedove' "$conf"
	done

	# Fix permissions on persistent directories that were created
	# with unsafe permissions.
	for persistent_fs in $(ls -d /live/persistence/*_unlocked || true)
	do
		[ -d "$persistent_fs" ] || continue
		for child in $(ls "$persistent_fs" || true)
		do
			subdir="$persistent_fs/$child"
			[ -d "$subdir" ] || continue
			# Note: we chmod even custom persistent directories.
			# This may break things by changing otherwise correct
			# permissions copied from the directory that was made
			# persistent, so we only do that if the persistent directory
			# is owned by amnesia:amnesia, and thus unlikely to be
			# a system directory. This e.g. avoids setting wrong
			# permissions on the APT, CUPS and NetworkManager
			# persistent directories.
			[ $(stat -c '%U' "$subdir") = 'amnesia' ] || continue
			[ $(stat -c '%G' "$subdir") = 'amnesia' ] || continue
			if [ "$PERSISTENCE_READONLY" = true ]
			then
				warning "Permissions of '$subdir' may need to be fixed, but read only was selected; please retry in read-write mode"
			else
				chmod go= "$subdir"
			fi
		done
	done

	# Load the new persistence.conf.
	custom_mounts="$(mktemp /tmp/custom_mounts-XXXXXX.list)"
	get_custom_mounts ${custom_mounts} ${open_volumes}

	if [ -s "${custom_mounts}" ]
	then
		OLD_UMASK="$(umask)"
		# Have activate_custom_mounts create new directories
		# with safe permissions (#7443)
		umask 0077
		activate_custom_mounts ${custom_mounts} &> /dev/null
		umask "$OLD_UMASK"
	fi
	rm -f ${custom_mounts} 2> /dev/null

	for vol in ${open_volumes}
	do
		if grep -qe "^${vol}\>" /proc/mounts
		then
			successes="${successes} ${vol}"
		else
			failures="${failures} ${vol}"
			ret=1
		fi
	done

	if [ -n "${successes}" ]
	then
		echo "Successes:"
		for vol in ${successes}
		do
			echo "  - ${vol}"
		done
	fi

	if [ -n "${failures}" ]
	then
		echo "Failures:"
		for vol in ${failures}
		do
			echo "  - ${vol}"
		done
	fi
	exit ${ret}
}

close_volumes ()
{
	local volumes=${@}
	local custom_mounts="$(mktemp /tmp/custom_mounts-XXXXXX.list)"
	get_custom_mounts ${custom_mounts} ${volumes}
	while read device source dest options # < ${custom_mounts}
	do
		if [ "${options}" != linkfiles ]
		then
			umount ${dest} 2> /dev/null
		fi
	done < ${custom_mounts}
	rm -f ${custom_mounts} 2> /dev/null
	for vol in ${volumes}
	do
		local backing=$(where_is_mounted ${vol})
		umount ${backing}
	done
}

main ()
{
	# tracing get's activated by Arguments() if "debug" is in /proc/cmdline
	# which may be something we don't want to flood stderr
	exec 3<>"/dev/null"
	BASH_XTRACEFD="3"

	# parse the kernel cmdline for live-boot's configuration as defaults
	Cmdline_old

	# note that this is not enough for disabling tracing. we need to do the
	# $BASH_XTRACEFD stuff above to avoid tracing until this point.
	set +x

	export PERSISTENCE="true"
	export NOPERSISTENCE=""

	# Should be set empty since live-boot already changed root for us
	export rootmnt=""

	while echo "${1}" | grep -qe "^--[^ ]\+\>"
	do
		case "${1}" in
			--encryption=*)
				export PERSISTENCE_ENCRYPTION="${1#*=}"
				;;
			--help)
				usage
				exit 0
				;;
			--log-file=*)
				local log_file="${1#*=}"
				[ -e "${log_file}" ] && rm -f "${log_file}"
				exec 3<>"${log_file}"
				set -x
				;;
			--media=*)
				export PERSISTENCE_MEDIA="${1#*=}"
				;;
			--read-only)
				export PERSISTENCE_READONLY="true"
				;;
			--read-write)
				export PERSISTENCE_READONLY=""
				;;
			--union=*)
				export UNIONTYPE="${1#*=}"
				;;
			*)
				error "unrecognized option: ${1}"
				;;
		esac
		shift
	done

	local action="${1}"
	shift
	case "${action}" in
		list)
			local labels=${@}
			if ! echo ${labels} | grep -qe "[^[:space:]]"
			then
				# use default from live-helpers
				labels=${custom_overlay_label}
			fi
			list_gpt_volumes ${labels}
			;;
		activate|close)
			if ! echo ${@} | grep -qe "[^[:space:]]"
			then
				error "you must specify at least one volume"
			fi
			${action}_volumes "${@}"
			;;
		"")
			error "no action specified"
			;;
		*)
			error "unrecognized action: ${action}"
			;;
	esac
}

main ${@}
